/*
 * ManagerImpl.h
 *  Androidの場合はAndroidManifestに次のパーミッションを追加
 *  <uses-permission android:name="android.permission.INTERNET"/>
 *
 *  Created on: 2012/02/05
 *      Author: takedanaoki
 */

#ifndef INCLUDED_NTENGINE_NET_MANAGERIMPL_H
#define INCLUDED_NTENGINE_NET_MANAGERIMPL_H

#include "NTEngine/Base/Buffer.h"
#include "NTEngine/Base/AutoPtr.h"
#include "NTEngine/Base/DebugStream.h"
#include "NTEngine/Threading/Thread.h"
#include "NTEngine/Threading/Mutex.h"
#include "NTEngine/Threading/Event.h"
#include "NTEngine/Threading/Function.h"

#include "Net/src/HttpImpl.h"
#include "Net/src/Socket.h"
#include <string>
#include <sstream>
#include <list>
#include <map>
using namespace std;

namespace NTEngine {
using namespace Threading;
namespace Net {

/**
 * @brief Netマネージャ
 */
class ManagerImpl {
public:
	class HttpRequest {
	public:
		HttpRequest(
			const char* szUrl,
			const char* szMethod,
			const char* szBody,
			const char** headers, int headerLength,
			unsigned id
		) : mId( id ) {
			string url( szUrl );
			int npos = url.find( "://" );
			mService = url.substr( 0, npos );
			ASSERT( mService == "http" || mService == "https" ); // ちゃんと入っているか
			url = url.substr( npos + 3 );
			string path;
			npos = url.find( "/" );
			if ( npos == string::npos ) {
				path = "/";
				mHost = url;
			} else {
				path = url.substr( npos + 1 );
				mHost = url.substr( 0, npos );
			}
			// ヘッダ作成
			ostringstream oss;
			oss << szMethod << " " << path << " HTTP/1.1¥r¥n" // メソッド、パス
				<< "Host :" << mHost << "¥r¥n"; // ホスト
			for ( int i = 0; i < headerLength; ++i ) {
				// 追加ヘッダ
				int idx = i * 2;
				oss << headers[ idx ] << ": " << headers[ idx + 1 ] << "¥r¥n";
			}
			oss << "¥r¥n"; // ヘッダ終わりの改行
			// BODY
			if ( szBody ) {
				oss << szBody << "¥r¥n";
			}
			mHeader = oss.str();
		}
		~HttpRequest() {
		}
		const unsigned mId;
		string mService;
		string mHost;
		string mHeader;
	};

	class LoadThread : public Threading::Thread {
	public:
		LoadThread( ManagerImpl* manager ) : mManager( manager ){
		}
		~LoadThread(){
			join(); //終了を待って
			mManager = 0; //変数を捨てる
		}
	private:
		void operator()(){
			//終了要請されない間無限ループ
			while ( !mManager->isEndRequested() ){
				mManager->update();
			}
		}
		ManagerImpl* mManager;
	};

	typedef map< unsigned, Http::Impl* > HttpMap;
	typedef HttpMap::iterator HttpIt;
	typedef HttpMap::const_iterator ConstHttpIt;
	typedef list< HttpRequest* > HttpList;

	// --------------------------------------------------------------
	ManagerImpl() : mHttpRequestId( 0 ), mTimeoutSec( 30 * CLOCKS_PER_SEC ), mTotalSize( 0 ), mHaltOnError( false ) {
		mMutex = Threading::Mutex::create();
		mEndFlag = Threading::Event::create();
		mThread = NEW LoadThread( this );
		mThread->start();
	}

	// --------------------------------------------------------------
	~ManagerImpl() {
		// スレッドを終了させます
		mEndFlag.set();
		SAFE_DELETE( mThread );
		// 残っているかチェック
		if ( !mHttps.empty() ) {
#ifdef _DEBUG
			cout << "Net::Manager : you must destroy all http clients!" << endl;
#endif
			for ( HttpIt it = mHttps.begin(); it != mHttps.end(); ++it ) {
				SAFE_DELETE( it->second );
			}
			mHttps.clear();
		}
		if ( !mHttpRequests.empty() ) {
#ifdef _DEBUG
			cout << "Net::Manager : you must wait all http requests!" << endl;
#endif
			for ( HttpList::iterator it = mHttpRequests.begin(); it != mHttpRequests.end(); ++it ) {
				SAFE_DELETE( *it );
			}
			mHttpRequests.clear();
		}
	}

	// --------------------------------------------------------------
	void enableHaltOnError( bool f ){
		mHaltOnError = f * CLOCKS_PER_SEC;
	}

	// --------------------------------------------------------------
	void setTimeout( int sec ) {
		mTimeoutSec = sec;
	}

	// --------------------------------------------------------------
	void update() {
		//ロード要求取り出し
		mMutex.lock();
		if ( mHttpRequests.size() == 0 ){ //ここで調べないとダメ
			mMutex.unlock();
			// やることないので寝る
			Threading::sleep( 10 );
			return;
		}
		AutoPtr< HttpRequest > req( mHttpRequests.front() ); //現物コピー
		mHttpRequests.pop_front();
		mMutex.unlock();

#ifdef _DEBUG
		ostringstream oss;
#endif
		bool error = false;
		bool notification = false; // エラーをハンドルに送るか

		int respCode = 0; // httpヘッダコード
		int respSize = 0; // httpヘッダに記述されたbody部のサイズ
		int totalSize = 0; // 通信パケット合計
		char* buffer = 0; // body部リアルバッファ
		int size = 0; // body部リアルサイズ

		// 接続確認
		Socket sock( mTimeoutSec );
		if ( sock.open( req->mHost.c_str(), req->mService.c_str() ) ) {
			// Socketは開けた訳だが、始める前にハンドルがある？
			mMutex.lock();
			HttpIt it = mHttps.find( req->mId );
			if ( ( it == mHttps.end() ) || it->second->isFinished() ) {
				// ハンドルなかったり、すでに終わってたりしたらエラー処理
				error = true;
			}
			mMutex.unlock();
		} else {
#ifdef _DEBUG
			oss.str( "" );
			oss << "Net : can't open socket![" << req->mId << "](" << req->mHost << ")";
			cout << oss.str() << endl;
#endif
			if ( mHaltOnError ) {
				HALT( "Net : can't open socket!" );
			}
			error = notification = true;
		}

		// ここまでエラーある？なければリクエスト送信する
		if ( !error ) {
			// 一度にすべて遅れるとは限らない
			const char* p = req->mHeader.c_str();
			int len = static_cast< int >( req->mHeader.length() );
			while ( len > 0 ) {
				int n = sock.send( p, len );
				if ( n < 0 ) {
#ifdef _DEBUG
					oss.str( "" );
					oss << "Net : can't send buffer![" << req->mId << "](" << req->mHost << ")";
					cout << oss.str() << endl;
#endif
					if ( mHaltOnError ) {
						HALT( "Net : can't send buffer!" );
					}
					error = notification = true;
					break;
				}
				len -= n;
				p += n;
				// パケット通信量
				totalSize += n;
			}
		}

		// ここまでエラーある？なければヘッダ解析する
		if ( !error ) {
			string param;
			while ( 1 ) {
				char buf[ 2 ] = { 0 };
				int n = sock.recv( buf, 1 );
				if ( n > 0 ) {
					totalSize += n;
					if ( buf[ 0 ] == '\n' ) {
						if ( param.empty() ) {
							break;
						}
						int npos;
						if (
							( ( npos = param.find_last_of( "HTTP/1.0 " ) ) != string::npos ) ||
							( ( npos = param.find_last_of( "HTTP/1.1 " ) ) != string::npos )
						) {
							istringstream iss( param.substr( npos ) );
							iss >> respCode;
						} else if ( ( npos = param.find_last_of( "Content-Length: " ) ) != string::npos ) {
							istringstream iss( param.substr( npos ) );
							iss >> respSize;
						}
						param.clear();
					} else if ( buf[ 0 ] != '\r' ) {
						param.append( buf );
					}
				} else {
#ifdef _DEBUG
					oss.str( "" );
					oss << "Net : can't header recv buffer![" << req->mId << "](" << req->mHost << ")";
					cout << oss.str() << endl;
#endif
					if ( mHaltOnError ) {
						HALT( "Net : can't recv!" );
					}
					error = notification = true;
					break;
				}
			}
		}

		// ここまでエラーある？なければレスポンス受信する
		if ( !error ) {
			int bodySize = 0;
			Buffer< char > recvBuf( maxRecvBufferSize );
			while ( 1 ) {
				int n = sock.recv( recvBuf.add(), maxRecvBufferSize );
				// 相手CLOSE？
				if ( n == 0 ) {
					if ( bodySize != respSize ) {
						// ヘッダに書いてあるのと実際に取得できたサイズが違う。通信切れたかな。
						if ( mHaltOnError ) {
							HALT( "Net : response size error!" );
						}
						error = notification = true;
					}
					break;
				} else if ( n < 0 ) {
#ifdef _DEBUG
					oss.str( "" );
					oss << "Net : can't body recv buffer![" << req->mId << "](" << req->mHost << ")";
					cout << oss.str() << endl;
#endif
					if ( mHaltOnError ) {
						HALT( "Net : can't recv!" );
					}
					error = notification = true;
					break;
				}
				bodySize += n;
			}
			// 完了。
			size = recvBuf.size();
			buffer = NEW char[ size ];
			recvBuf.copyTo( buffer );
			totalSize += bodySize;
		}
		// 最終処理
		mMutex.lock();
		mTotalSize += totalSize;
		// 受け皿が存在するか最後に確認
		HttpIt it = mHttps.find( req->mId );
		if ( it == mHttps.end() ) {
			error = true;
		}
		//結果通知
		if ( error ) {
			SAFE_DELETE_ARRAY( buffer );
			if ( notification && ( it != mHttps.end() ) ) {
				it->second->setError();
			}
		} else {
			it->second->set( buffer, size, respCode );
		}
		mMutex.unlock();
	}

	// --------------------------------------------------------------
	bool isEndRequested() const {
		return mEndFlag.isSet();
	}

	// --------------------------------------------------------------
	Http::Impl* createHttp( const char* url, const char* method, const char* body, const char** headers, int headerLength ){
		mMutex.lock();
		//ハンドル生成
		Http::Impl* f = NEW Http::Impl( mHttpRequestId );
		//リクエスト生成
		HttpRequest* req = NEW HttpRequest( url, method, body, headers, headerLength, mHttpRequestId );
		//ハンドル、リクエスト追加
		mHttps.insert( make_pair( mHttpRequestId, f ) );
		mHttpRequests.push_back( req );
		++mHttpRequestId; //次に備えてインクリメント
		mMutex.unlock();

		return f;
	}

	// --------------------------------------------------------------
	void destroyHttp( Http::Impl* f ) {
		mMutex.lock();
		//こいつが最後なら削除
		if ( f->referenceCount() == 0 ){
			HttpIt it = mHttps.find( f->id() );
			HARD_ASSERT( it != mHttps.end() );
			mHttps.erase( it );
			SAFE_DELETE( f );
		}
		mMutex.unlock();
	}

	Mutex mMutex;
	LoadThread* mThread;
	Event mEndFlag;
	unsigned mHttpRequestId;

	int mTimeoutSec; // タイムアウト用
	int mTotalSize; // パケット量
	bool mHaltOnError; // エラーのとき止める？

	HttpList mHttpRequests;
	HttpMap mHttps;

	static const int maxRecvBufferSize = 1024;
};

extern ManagerImpl* gManagerImpl;

} // namespace Net
} // namespace NTEngine

#endif // INCLUDED_NTENGINE_NET_MANAGERIMPL_H
